package com.example.filter;

import com.alibaba.fastjson2.JSON;
import com.example.constant.RedisConstants;
import com.example.domain.LoginUser;
import com.example.utils.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * OncePerRequestFilter 是 Spring Web 提供的过滤器基类，保证同一个请求生命周期里只执行一次你的过滤逻辑（doFilterInternal）。
 * 为什么需要它？因为一次请求在 Servlet 容器里可能会发生多种派发（REQUEST → FORWARD/INCLUDE/ERROR/ASYNC），普通 Filter 可能被重复调用；继承它能避免重复执行导致的副作用（比如重复鉴权、重复记录日志、重复修改请求体等）。
 * Filter 属于 Servlet 规范；OncePerRequestFilter 属于 Spring（spring-web 模块）。
 * 但要注意：OncePerRequestFilter 也实现了 Servlet 的 Filter 接口，本质上仍是一个 Servlet 过滤器，只是由 Spring 提供的“更好用的基类”。
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 它如何做到“只执行一次”？
     * OncePerRequestFilter 的 doFilter(...)（父类实现）会做三件事：
     * 1、判重：在 HttpServletRequest 上放一个“已执行”标志（请求属性，名字可通过 getAlreadyFilteredAttributeName() 派生），如果发现已经处理过，就直接放行给下一个 Filter。
     * 2、可跳过：提供钩子让你“在特定情况下不执行”，常用有：
     * protected boolean shouldNotFilter(HttpServletRequest request)：按URL/path、方法等条件跳过。
     * protected boolean shouldNotFilterAsyncDispatch()：是否跳过异步派发（默认 false，即不跳过，会参与异步派发）。
     * protected boolean shouldNotFilterErrorDispatch()：是否跳过错误派发（默认 false）。
     * 3、真正执行业务：只在需要时调用你覆写的 doFilterInternal(request, response, chain)。
     * 小结：你只需要实现 doFilterInternal；其余“判重/条件跳过”的细节父类都帮你兜住了。
     *
     * @param request
     * @param response
     * @param filterChain
     * @throws ServletException
     * @throws IOException
     */

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            // 放行
            filterChain.doFilter(request, response);
            return; // 跳过当前过滤器
        }
        // 解析token
        String username = jwtUtil.getUsername(token);
        if (!StringUtils.hasText(username)) {
            throw new RuntimeException("Token 错误！");
        }
        // 从redis中获取用户信息
        String information = stringRedisTemplate.opsForValue().get(RedisConstants.USER_TOKEN + username);
        LoginUser loginUser = JSON.parseObject(information, LoginUser.class);
        if (loginUser == null) {
            throw new RuntimeException("用户未登录！");
        }
        /**
         * JwtAuthenticationTokenFilter 里放进 SecurityContextHolder 的 Authentication，后续 同一条 SecurityFilterChain 中的授权环节 就会拿它来做鉴权。
         * SecurityContextHolder 里放的是“当前用户是谁以及拥有什么权限”的凭据。
         * 授权（无论是 authorizeHttpRequests 还是 @PreAuthorize）都会 读取这里的 Authentication 来做决策。
         * 没有凭据 → 401；有凭据但不够权限 → 403；满足规则 → 放行。
         */
        // 存入SecurityContextHolder
        // 获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authentication); // 设置认证信息
        // 放行
        filterChain.doFilter(request, response);
    }

}
/**
 * 为什么需要保存在SecurityContextHolder中？
 * SecurityContextHolder是Spring Security的核心组件，用于存储当前安全上下文信息，主要有以下作用：
 * 身份认证状态保持：在整个请求处理周期中保持用户的认证状态
 * 授权决策基础：后续的授权检查（如@PreAuthorize、URL权限检查等）都依赖于此处的认证信息
 * 线程局部存储：使用ThreadLocal实现，确保每个请求的认证信息隔离
 * 框架集成：Spring Security的其他组件（如方法级安全、视图层安全等）都依赖于SecurityContextHolder中的信息
 * ---
 * 核心原因：无状态与线程隔离
 * HTTP 协议的无状态性：HTTP 请求本身是无状态的。每个请求到达服务器时，服务器并不知道它之前的请求做了什么。因此，服务器必须在每个请求中重新建立“谁在请求（认证）”和“他能做什么（授权）”这个上下文。
 * 线程隔离：现代 Web 服务器（如 Tomcat）使用线程池来处理并发请求。一个线程处理完一个用户的请求后，会被放回池中，接下来可能被用来处理另一个完全不同用户的请求。
 * 问题：如果认证信息只是简单地存储在某个线程的变量里，那么当这个线程被重用时，下一个请求可能会错误地“看到”上一个用户的认证信息，这将造成灾难性的安全问题（例如，用户A看到了用户B的数据）。
 */